package com.ddd.mill.infrastructure.utils;

import com.ddd.mill.infrastructure.redis.utils.RedisUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 订单号生成
 *
 * 生成规则中会替换的元素：
 *      yyyy ：年
 *      yy ：年 (后两位)
 *      MM ：月
 *      dd ：日
 *      hh ：时
 *      mm ：分
 *      ss ：秒
 *      ran{x} ：x位的随机数
 *      serial{x} : x位的流水号
 *
 * @description:
 * @author: zhoukunhua
 * @date: 2020-01-16 7:40 PM
 */
@Component
public class OrderNoGenerator {
    private static final String ranRegex = "ran\\{\\d+\\}";
    private static final String serialRegex = "serial\\{\\d+\\}";
    @Autowired
    private SnowFlake snowFlake;
    @Autowired
    private RedisUtil redisUtil;

    public String nextEquipmentNo(String model){
        model = "QyyyyMMran{8}";
        return nextNo(model, "EquipmentNo", true);
    }

    public String nextSaleOrderNo(String model){
        return nextNo(model, "SaleOrderNo", true);
    }

    public String nextQnOrderNo(String model){
        return nextNo(model, "QnOrderNo", true);
    }

    public String nextProductOrder(String model){
        return nextNo(model, "ProductOrderNo", true);
    }

    /**
     * 获得订单号
     * @param format 格式
     * @param key 订单号类型，用于作为redis中自增数的key
     * @return
     */
    public synchronized String nextNo(String format, String key){
        return nextNo(format, key, false);
    }

    /**
     * 获得订单号
     * @param format 格式
     * @param key 订单号类型，用于作为redis中自增数的key
     * @param isSerial 是否有流水号
     * @return
     */
    public synchronized String nextNo(String format, String key, boolean isSerial){
        LocalDateTime localDateTime = LocalDateTime.now();
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.MONTH, 0);
        calendar.set(Calendar.DAY_OF_MONTH, 1);
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        int field = 0;
        // 处理固定格式
        for (Element e : Element.values()){
            String value = "";
            switch (e){
                case yyyy : value = fillZero(localDateTime.getYear(),2);
                            if (format.indexOf("yyyy") > -1){
                                calendar.set(Calendar.YEAR, localDateTime.getYear());
                                field = Calendar.YEAR;
                            }
                            break;
                case yy : value = fillZero(localDateTime.getYear() % 100,2);
                            if (format.indexOf("yy") > -1) {
                                calendar.set(Calendar.YEAR, localDateTime.getYear());
                                field = Calendar.YEAR;
                            }
                            break;
                case MM : value = fillZero(localDateTime.getMonthValue(),2);
                            if (format.indexOf("MM") > -1){
                                calendar.set(Calendar.MONTH, localDateTime.getMonthValue() - 1);
                                field = Calendar.MONTH;
                            }
                            break;
                case dd: value = fillZero(localDateTime.getDayOfMonth(),2);
                            if (format.indexOf("dd") > -1){
                                calendar.set(Calendar.DAY_OF_MONTH, localDateTime.getDayOfMonth());
                                field = Calendar.DAY_OF_MONTH;
                            }
                            break;
                case hh: value = fillZero(localDateTime.getHour(),2);
                            if (format.indexOf("hh") > -1){
                                calendar.set(Calendar.HOUR_OF_DAY, localDateTime.getHour());
                                field = Calendar.HOUR_OF_DAY;
                            }
                            break;
                case mm: value = fillZero(localDateTime.getMinute(),2);
                            if (format.indexOf("mm") > -1){
                                calendar.set(Calendar.MINUTE, localDateTime.getMinute());
                                field = Calendar.MINUTE;
                            }
                            break;
                case ss: value = fillZero(localDateTime.getSecond(),2);
                            if (format.indexOf("ss") > -1){
                                calendar.set(Calendar.SECOND, localDateTime.getSecond());
                                field = Calendar.SECOND;
                            }
                            break;
                default:
            }
            format = format.replaceAll(e.name(), value);
        }
        calendar.set(field, calendar.get(field) + 1);
        Long expireTime = 0L;
        if (isSerial){
            expireTime = calendar.getTimeInMillis() / 1000;
        }
        // 处理流水号
        Pattern pattern = Pattern.compile(serialRegex);
        Matcher matcher = pattern.matcher(format);
        while (matcher.find()){
            String s = matcher.group();
            int bitNumber = Integer.parseInt(s.replace("serial{", "").replace("}", ""));
            format = format.replace(s, increaseNumber(bitNumber, format.substring(0, format.indexOf("serial"))));
        }
        // 处理随机数
        pattern = Pattern.compile(ranRegex);
        matcher = pattern.matcher(format);
        while (matcher.find()){
            String s = matcher.group();
            int bitNumber = Integer.parseInt(s.replace("ran{", "").replace("}", ""));
            if (isSerial) {
                format = format.replace(s, increaseNumber(bitNumber, key, expireTime));
            }else if (bitNumber < 6){
                format = format.replace(s, increaseNumber(bitNumber, key));
            }else if (bitNumber >=6 && bitNumber <= 13){
                format = format.replace(s, increaseNumber(bitNumber, key));
//                format = format.replace(s, timeNumber(bitNumber));
            }else if (bitNumber > 13){
                format = format.replace(s, snowNumber(bitNumber));
            }
        }

        return format;
    }

    // 6~13位随机数生成
    private String timeNumber(int bitNumber){
        if (bitNumber > 13){
            bitNumber = 13;
        }
        Long num = System.currentTimeMillis();
        int cutEnd = new Double((13 - bitNumber) / 2).intValue();
        int cutTop = 13 - bitNumber - cutEnd;
        String s = num.toString().substring(cutTop);
        return s.substring(0, s.length() - cutEnd);
    }

    // 13~19位随机数生成
    private String snowNumber(int bitNumber){
        if (bitNumber > 19){
            bitNumber = 19;
        }
        Long num = snowFlake.nextId();
        int cutEnd = new Double((19 - bitNumber) / 2.5).intValue();
        int cutTop = 19 - bitNumber - cutEnd;
        String s = num.toString().substring(cutTop);
        return s.substring(0, s.length() - cutEnd);
    }

    // 自增数
    private String increaseNumber(int bitNumber, String key){
        Long n = redisUtil.incr(key, 1);
        String s = n.toString();
        if (s.length() >= bitNumber){
            return s.substring(s.length() - bitNumber);
        }
        return fillZero(n, bitNumber);
    }

    // 自增数
    private String increaseNumber(int bitNumber, String key, Long expireTime){
        Map<String, Long> map = (Map<String, Long>)redisUtil.get(key);
        if (map == null || !map.get("expire").equals(expireTime)){
            map = new HashMap<>();
            map.put("value", 1L);
            map.put("expire", expireTime);
        }else {
            map.put("value", map.get("value") + 1);
        }
        redisUtil.set(key, map);
        Long n = map.get("value");
        String s = n.toString();
        if (s.length() >= bitNumber){
            return s.substring(s.length() - bitNumber);
        }
        return fillZero(n, bitNumber);
    }

    // 补零
    private String fillZero(long n, int bitNumber){
        String s = n + "";
        int zeroNum = bitNumber - s.length();
        if (zeroNum > 0) {
            String fill = new Double(Math.pow(10, zeroNum)).intValue() + "";
            s = fill.substring(1) + s;
        }
        return s;
    }

    enum Element{
        yyyy, yy, MM, dd, hh, mm, ss
    }
}